// @ts-check
/** @typedef {{shareScope:string,internalRequest:string,request:string}} RemoteModule */

module.exports.SanRemoteModulePlugin = class SanRemoteModulePlugin {
  constructor() {
    this.name = "SanRemoteModulePlugin";
    /** @type {{[k:string]:{[a:string]:Set<string>}}} */
    this.stat = {};
  }
  /**
   * @param {import('webpack').Compiler} compiler
   */
  apply(compiler) {
    compiler.hooks.make.tapAsync(this.name, (compilation, callback) => {
      compilation.processDependenciesQueue.hooks.added.tap(
        this.name,
        (module) => {
          if ("remote-module" === module.type) {
            const md = /** @type {RemoteModule} */ (
              /**@type {unknown}*/ (module)
            );
            const {
              shareScope: scope,
              internalRequest: req,
              request: fullReq,
            } = md;
            const app = fullReq.slice(0, fullReq.length + 1 - req.length);
            if (!scope || !app || !req) {
              console.warn(`【${this.name}】: 远程模块信息提取失败！`, md);
            }
            if (!this.stat[scope]) {
              this.stat[scope] = {};
            }
            if (!this.stat[scope][app]) {
              this.stat[scope][app] = new Set();
            }
            this.stat[scope][app].add(req);
          }
        }
      );
      compilation.hooks.seal.tap(this.name, () => {
        const resStr = JSON.stringify(
          this.stat,
          (_k, v) => {
            if (v instanceof Set) {
              return Array.from(v);
            }
            return v;
          },
          2
        );
        // TODO 处理结果
        const res = JSON.parse(resStr);
        console.log("远程模块：", res);
      });

      callback();
    });
  }
};

module.exports.ScanModuleOutFolder = class ScanModuleOutFolder {
  /**
   *
   * @param {{absoluteFolders:string|RegExp|Array<string|RegExp>}} param0
   */
  constructor({ absoluteFolders }) {
    this.name = "ScanModuleOutFolder";
    /** @type {Set<string>} */
    this.stat = new Set();
    /** @type {Array<string|RegExp>} */
    this.folders = [];
    if (Array.isArray(absoluteFolders)) {
      this.folders = absoluteFolders;
    } else if (absoluteFolders) {
      this.folders = [absoluteFolders];
    }
  }
  /**
   * @param {import('webpack').Compiler} compiler
   */
  apply(compiler) {
    compiler.hooks.make.tapAsync(this.name, (compilation, callback) => {
      compilation.processDependenciesQueue.hooks.added.tap(
        this.name,
        (module) => {
          if (
            [
              "remote-module",
              "provide-module",
              "consume-shared-module",
            ].includes(module.type)
          ) {
            // "remote-module" 最终解析的远程模块 对应 RemoteModule
            // "provide-module","consume-shared-module" 联邦模块中的shared方案：分别对应：ProvideSharedModule 和 ConsumeSharedModule
            return;
          }

          if ("javascript/dynamic" === module.type) {
            // 对应 ContextModule DelegatedModule DllModule ExternalModule RawModule ContainerEntryModule
            // @ts-ignore
            if (module._exposes) {
              // 联邦模块expose共享出去的 对应： ContainerEntryModule
              return;
            }
            // @ts-ignore
            if (module.externalType === "promise") {
              // 引用远程联邦模块的Promise代码，对应： ExternalModule
              return;
            }
          }

          const md = /** @type {any} */ (module);

          // 如：'javascript/auto' 只对应 NormalModule，但NormalModule有多种type
          if (md.userRequest) {
            this.stat.add(md.userRequest);

            return;
          }

          // 如：'css/mini-extract' 插件自定义类型！对应： CssModule
          // 如：'javascript/esm' 包括 require.context() 产生的 ConcatenatedModule
          if (md._identifier && md.context) {
            const path = md._identifier.split(md.context)[1];
            if (path) {
              this.stat.add(md.context + path);

              return;
            }
          }

          if (md.identifierStr) {
            // 某些 javascript/dynamic 如 object-inspect@1.12.3 下的文件：util.inspect.js
            this.stat.add(md.identifierStr);

            return;
          }

          console.warn(`【${this.name}】： 未支持的模块类型：`, module);
        }
      );
      compilation.hooks.seal.tap(this.name, () => {
        const res = [];
        const _fld = this.folders.map((str) =>
          typeof str === "string" ? str.replace(/\\/g, "/") : str
        );
        const _stat = Array.from(this.stat).map((str) =>
          str.replace(/\\/g, "/")
        );

        file: for (const f of _stat) {
          folder: for (const d of _fld) {
            if (typeof d === "string") {
              if (f.startsWith(d)) {
                continue file;
              }
            } else {
              if (d.test(f)) {
                continue file;
              }
            }
          }
          res.push(f);
        }

        // TODO 处理结果
        console.log("不在指定folder中的依赖：", res);
      });

      callback();
    });
  }
};

module.exports.ListenAssetsJsPlugin = class RemoteAppInfoPlugin {
  /** @param {(filename:string)=>boolean} handler 终止循环请返回truthy */
  constructor(handler = () => true) {
    this.name = "RemoteAppInfoPlugin";
    this.handler = handler;
  }
  /**
   * @param {import('webpack').Compiler} compiler
   */
  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync(this.name, (a1, callback) => {
      const jsFiles = Object.keys(a1.assets).filter((file) =>
        file.endsWith(".js")
      );
      for (const jsFile of jsFiles) {
        if (this.handler(jsFile)) {
          break;
        }
      }
      callback();
    });
  }
};

const fs = require("fs");
const path = require("path");
const configPath = path.resolve(__dirname, "../../appConfig.json");

/**
 *
 * @param {string} app
 * @param {string} uri
 */
module.exports.updateAppInfo = function (app, uri) {
  // TODO 根据实际情况获取公共配置，以及改写公共配置
  const str = fs.readFileSync(configPath, { encoding: "utf-8" });
  const obj = JSON.parse(str);

  obj[app] = uri;
  const newStr = JSON.stringify(obj, null, 2);
  fs.writeFileSync(configPath, newStr);
};
